/*
 * %W% %E%
 *
 * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package com.sun.corba.se.impl.presentation.rmi ;

import java.util.Map ;
import java.util.HashMap ;
import java.util.Set ;
import java.util.HashSet ;
import java.util.List ;
import java.util.ArrayList ;
import java.util.Iterator ;

import java.lang.reflect.Method ;

import java.rmi.Remote ;

import javax.rmi.CORBA.Tie ;

import com.sun.corba.se.spi.orbutil.proxy.InvocationHandlerFactory ;

import com.sun.corba.se.spi.presentation.rmi.IDLNameTranslator ;
import com.sun.corba.se.spi.presentation.rmi.DynamicMethodMarshaller ;
import com.sun.corba.se.spi.presentation.rmi.PresentationManager ;

import com.sun.corba.se.spi.logging.CORBALogDomains ;

import com.sun.corba.se.impl.logging.ORBUtilSystemException ;

import com.sun.corba.se.impl.presentation.rmi.IDLNameTranslatorImpl ;
import com.sun.corba.se.impl.presentation.rmi.StubFactoryProxyImpl ;

import com.sun.corba.se.impl.orbutil.graph.Node ;
import com.sun.corba.se.impl.orbutil.graph.Graph ;
import com.sun.corba.se.impl.orbutil.graph.GraphImpl ;

public final class PresentationManagerImpl implements PresentationManager
{
    private Map classToClassData ;
    private Map methodToDMM ;
    private PresentationManager.StubFactoryFactory staticStubFactoryFactory ;
    private PresentationManager.StubFactoryFactory dynamicStubFactoryFactory ;
    private ORBUtilSystemException wrapper = null ;
    private boolean useDynamicStubs ;

    public PresentationManagerImpl( boolean useDynamicStubs )
    {
	this.useDynamicStubs = useDynamicStubs ;
	wrapper = ORBUtilSystemException.get( 
	    CORBALogDomains.RPC_PRESENTATION ) ;

	// XXX these should probably be WeakHashMaps.
	classToClassData = new HashMap() ;
	methodToDMM = new HashMap() ;
    }

////////////////////////////////////////////////////////////////////////////////
// PresentationManager interface
////////////////////////////////////////////////////////////////////////////////

    public synchronized DynamicMethodMarshaller getDynamicMethodMarshaller( 
	Method method ) 
    {
	if (method == null)
	    return null ;

	DynamicMethodMarshaller result = 
	    (DynamicMethodMarshaller)methodToDMM.get( method ) ;
	if (result == null) {
	    result = new DynamicMethodMarshallerImpl( method ) ;
	    methodToDMM.put( method, result ) ;
	}

	return result ;
    }

    public synchronized ClassData getClassData( Class cls ) 
    {
	ClassData result = (ClassData)classToClassData.get( cls ) ;
	if (result == null) {
	    result = new ClassDataImpl( cls ) ;
	    classToClassData.put( cls, result ) ;
	}

	return result ;
    }

    private class ClassDataImpl implements PresentationManager.ClassData 
    {
	private Class cls ;
	private IDLNameTranslator nameTranslator ;
	private String[] typeIds ;
	private PresentationManager.StubFactory sfactory ;
	private InvocationHandlerFactory ihfactory ;
	private Map dictionary ;

	public ClassDataImpl( Class cls ) 
	{
	    this.cls = cls ;
	    Graph gr = new GraphImpl() ;
	    NodeImpl root = new NodeImpl( cls ) ;
	    Set rootSet = getRootSet( cls, root, gr ) ;

	    // At this point, rootSet contains those remote interfaces
	    // that are not related by inheritance, and gr contains
	    // all reachable remote interfaces.

	    Class[] interfaces = getInterfaces( rootSet ) ;
	    nameTranslator = IDLNameTranslatorImpl.get( interfaces ) ;
	    typeIds = makeTypeIds( root, gr, rootSet ) ;
	    ihfactory = new InvocationHandlerFactoryImpl( 
		PresentationManagerImpl.this, this ) ;
	    dictionary = new HashMap() ;
	}

	public Class getMyClass()
	{
	    return cls ;
	}

	public IDLNameTranslator getIDLNameTranslator()
	{
	    return nameTranslator ;
	}

	public String[] getTypeIds()
	{
	    return typeIds ;
	}

	public InvocationHandlerFactory getInvocationHandlerFactory() 
	{
	    return ihfactory ;
	}

	public Map getDictionary()
	{
	    return dictionary ;
	}
    }

    public PresentationManager.StubFactoryFactory getStubFactoryFactory( 
	boolean isDynamic ) 
    {
	if (isDynamic)
	    return dynamicStubFactoryFactory ;
	else
	    return staticStubFactoryFactory ;
    }

    public void setStubFactoryFactory( boolean isDynamic, 
	PresentationManager.StubFactoryFactory sff ) 
    {
	if (isDynamic)
	    dynamicStubFactoryFactory = sff ;
	else
	    staticStubFactoryFactory = sff ;
    }

    public Tie getTie()
    {
	return dynamicStubFactoryFactory.getTie( null ) ;
    }

    public boolean useDynamicStubs()
    {
	return useDynamicStubs ;
    }

////////////////////////////////////////////////////////////////////////////////
// Graph computations
////////////////////////////////////////////////////////////////////////////////

    private Set getRootSet( Class target, NodeImpl root, Graph gr ) 
    {
	Set rootSet = null ;

	if (target.isInterface()) {
	    gr.add( root ) ;
	    rootSet = gr.getRoots() ; // rootSet just contains root here
	} else {
	    // Use this class and its superclasses (not Object) as initial roots
	    Class superclass = target ;
	    Set initialRootSet = new HashSet() ;
	    while ((superclass != null) && !superclass.equals( Object.class )) {
		Node node = new NodeImpl( superclass ) ;
		gr.add( node ) ;
		initialRootSet.add( node ) ;
		superclass = superclass.getSuperclass() ;
	    }

	    // Expand all nodes into the graph
	    gr.getRoots() ; 

	    // remove the roots and find roots again
	    gr.removeAll( initialRootSet ) ;
	    rootSet = gr.getRoots() ;    
	}

	return rootSet ;
    }

    private Class[] getInterfaces( Set roots )
    {
	Class[] classes = new Class[ roots.size() ] ;
	Iterator iter = roots.iterator() ;
	int ctr = 0 ;
	while (iter.hasNext()) {
	    NodeImpl node = (NodeImpl)iter.next() ;
	    classes[ctr++] = node.getInterface() ;
	}

	return classes ;
    }

    private String[] makeTypeIds( NodeImpl root, Graph gr, Set rootSet ) 
    {
	Set nonRootSet = new HashSet( gr ) ;
	nonRootSet.removeAll( rootSet ) ;

	// List<String> for the typeids
	List result = new ArrayList() ;

	if (rootSet.size() > 1) {
	    // If the rootSet has more than one element, we must
	    // put the type id of the implementation class first.
	    // Root represents the implementation class here.
	    result.add( root.getTypeId() ) ;
	}

	addNodes( result, rootSet ) ;
	addNodes( result, nonRootSet ) ;

	return (String[])result.toArray( new String[result.size()] ) ;
    }

    private void addNodes( List resultList, Set nodeSet )
    {
	Iterator iter = nodeSet.iterator() ;
	while (iter.hasNext()) {
	    NodeImpl node = (NodeImpl)iter.next() ;
	    String typeId = node.getTypeId() ;
	    resultList.add( typeId ) ;
	}
    }

    private static class NodeImpl implements Node
    {
	private Class interf ;

	public Class getInterface()
	{
	    return interf ;
	}

	public NodeImpl( Class interf )
	{
	    this.interf = interf ;
	}

	public String getTypeId()
	{
	    return "RMI:" + interf.getName() + ":0000000000000000" ;
	}

	public Set getChildren()
	{
	    Set result = new HashSet() ;
	    Class[] interfaces = interf.getInterfaces() ;
	    for (int ctr=0; ctr<interfaces.length; ctr++) {
		Class cls = interfaces[ctr] ;
		if (Remote.class.isAssignableFrom(cls) &&
		    !Remote.class.equals(cls))
		    result.add( new NodeImpl( cls ) ) ;
	    }

	    return result ;
	}

	public String toString() 
	{
	    return "NodeImpl[" + interf + "]" ;
	}

	public int hashCode()
	{
	    return interf.hashCode() ;
	}

	public boolean equals( Object obj )
	{
	    if (this == obj)
		return true ;

	    if (!(obj instanceof NodeImpl))
		return false ;

	    NodeImpl other = (NodeImpl)obj ;

	    return other.interf.equals( interf ) ;
	}
    }
}
